#include <iostream>
#include <math.h>
#include <limits.h>
#include "ALU_DEF.h"
#include "ALU_MUL.h"

using namespace std;

ALU_MUL::ALU_MUL()
{
	Ready = true;
	inputA = 0;
	inputB = 0;
	outputZ = 0;
	OpCode = NOP_OPCODE;
	CyclesLeft = 0;
	regDst = 0;
	fu_lrf = NULL;
}

void ALU_MUL::update()
{
	ALU_MUL_ENTRY temp;
	ALU_MUL_ENTRY temp2;
	int in_flight = (int) mul_pipeline.size();
	temp.Ready = true;
	temp.OpCode = NOP_OPCODE;

	update_queue();			//Get the current instruction going

	if (Ready == false)		//If the instruction did not complete store the values
	{
		temp.CyclesLeft = CyclesLeft;
		temp.inputA = inputA;
		temp.inputB = inputB;
		temp.inputFA = inputFA;
		temp.inputFB = inputFB;
		temp.OpCode = OpCode;
		temp.outputFZ = outputFZ;
		temp.outputZ = outputZ;
		temp.Ready = Ready;
		temp.regDst = regDst;
	}

	for (int i = 0; i < in_flight; i++)		//for all in flight instructions update their progress
	{
		temp2 = mul_pipeline.front();
		mul_pipeline.pop();
		inputA = temp2.inputA;
		inputB = temp2.inputB;
		inputFA = temp2.inputFA;
		inputFB = temp2.inputFB;
		OpCode = temp2.OpCode;
		outputFZ = temp2.outputFZ;
		outputZ = temp2.outputZ;
		Ready = temp2.Ready;
		regDst = temp2.regDst;
		CyclesLeft = temp2.CyclesLeft;

		update_queue();

		if (Ready == false)				//If the instruction still has not finished put it back in the queue
		{
			temp2.CyclesLeft = CyclesLeft;
			temp2.inputA = inputA;
			temp2.inputB = inputB;
			temp2.inputFA = inputFA;
			temp2.inputFB = inputFB;
			temp2.OpCode = OpCode;
			temp2.outputFZ = outputFZ;
			temp2.outputZ = outputZ;
			temp2.Ready = Ready;
			temp2.regDst = regDst;
			mul_pipeline.push(temp2);
		}
	}

	if (temp.Ready == false && temp.OpCode != NOP_OPCODE)	//Push the new instruction onto the queue if it needs it
		mul_pipeline.push(temp);

	OpCode = NOP_OPCODE;
	Ready = true;
}

void ALU_MUL::update_queue()
{
	long long int temp = 0;
	unsigned long long int temp2= 0;
	unsigned int temp3 = 0;

	if(Ready)
	{
		if( NOP_OPCODE != OpCode )
		{
			switch (OpCode)
			{
			case IMUL_OPCODE:
				CyclesLeft = IMUL_DELAY;
				break;
			case UMUL_OPCODE:
				CyclesLeft = UMUL_DELAY;
				break;
			case IMULR_OPCODE:
				CyclesLeft = IMULR_DELAY;
				break;
			case UMULR_OPCODE:
				CyclesLeft = UMULR_DELAY;
				break;
			case IMULD_OPCODE:
				CyclesLeft = IMULD_DELAY;
				break;
			case UMULD_OPCODE:
				CyclesLeft = UMULD_DELAY;
				break;
			case FMUL_OPCODE:
				CyclesLeft = FMUL_DELAY;
				break;
			}

			Ready = false;
			update_queue();
		}
	}
	else // Not ready, processing operation
	{
		CyclesLeft--;

		if( 0 == CyclesLeft )
		{
			switch( OpCode )
			{
				case IMUL_OPCODE:
					outputZ = inputA * inputB;
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case UMUL_OPCODE:
					outputZ = (unsigned int) inputA * (unsigned int) inputB;
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case IMULR_OPCODE:
					outputZ = inputA * inputB;
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case UMULR_OPCODE:
					outputZ = (unsigned int) inputA * (unsigned int) inputB;
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case IMULD_OPCODE:
					outputZ = ((unsigned int )inputA & 0x0000FFFF) * ((unsigned int) inputB & 0x0000FFFF);
					if (((inputA < 0) && (inputB >= 0)) || ((inputA >= 0) && (inputB < 0)))
						outputZ = outputZ * -1;
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case UMULD_OPCODE:
					outputZ = ((unsigned int) inputA & 0x0000FFFF) * ((unsigned int) inputB & 0x0000FFFF);
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
				case FMUL_OPCODE:
					outputFZ = inputFA * inputFB;
					fu_lrf->write(regDst, (unsigned char) outputZ);
					break;
			}

			OpCode = NOP_OPCODE;
			Ready = true;
		}
	}
}

int ALU_MUL::getOutput()
{
	return outputZ;
}

float ALU_MUL::getOutputF()
{
	return outputFZ;
}

bool ALU_MUL::isReady()
{
	return Ready;
}

bool ALU_MUL::isFinished()
{
	return mul_pipeline.empty();
}
